package controllers

import command.CommandUtils

import java.io.File
import javax.inject.Inject
import org.apache.commons.io.FileUtils
import play.api.mvc.{AbstractController, ControllerComponents}
import tool.{Tool, _}
import utils.Utils
import models.Tables._
import org.joda.time.DateTime
import dao._
import org.apache.commons.lang3.StringUtils
import play.api.libs.json.{JsObject, Json}

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import implicits.Implicits._
import org.zeroturnaround.zip.ZipUtil
import shared.Shared
import shared.pojo.App._
import spdf._
import shared.pojo.Pojo.predict._
import upickle.default._

/** Created by yz on 2019/1/3
  */
class PredictController @Inject() (
    cc: ControllerComponents,
    formTool: FormTool,
    tool: Tool,
    missionDao: MissionDao,
    extraDataDao: ExtraDataDao,
    trashDao: TrashDao
)(implicit val userDao: UserDao,
  implicit val pdfInfoDao: PdfInfoDao,
) extends AbstractController(cc) {

  def predictBefore = Action { implicit request =>
    Ok(views.html.user.predict())
  }

  def predict = Action.async { implicit request =>
    val data = formTool.predictForm.bindFromRequest().get
    val userId = tool.getUserId
    val myMessage = Tool.dataCheck(data).toMyMessage
    if (myMessage.valid) {
      val tmpDir = tool.createTempDirectory("tmpDir")
      val inputFile = new File(tmpDir, "input.txt")
      val lines =
        ArrayBuffer(ArrayBuffer("SampleID", "Age", "AST", "ALT", "PLT"))
      lines += ArrayBuffer(
        data.sampleId,
        data.age,
        data.ast,
        data.alt,
        data.plt
      )
      Utils.lines2File(inputFile, lines.map(_.mkString("\t")))
      Utils.txt2Xlsx(inputFile, new File(tmpDir, "input.xlsx"))
      FileUtils.copyFileToDirectory(
        new File(Utils.dataDir, "LiveBoost_load.RData"),
        tmpDir
      )
      val command =
        s"""
           |dos2unix *
           |Rscript ${Utils.rPath.unixPath}/predict_v1.R
         """.stripMargin
      val execCommand =
        CommandUtils.orderCallLinuxScript(tmpDir, shBuffer = List(command))
      if (execCommand.isSuccess) {
        val outFile = new File(tmpDir, "out.txt")
        val resultData = tool.getResultData(outFile)
        val score = Shared.calculateScore(resultData)
        val result = Shared.getResult(score)
        val time = new DateTime()
        val row = MissionRow(
          0,
          userId,
          data.sampleId,
          data.name,
          data.age,
          data.ast,
          data.alt,
          data.plt,
          score.toString(),
          result,
          time
        )
        missionDao.insertAndRetunId(row).flatMap { missionId =>
          val extraRow = tool
            .getEmptyExtraDataRow(missionId, userId)
            .copy(
              sampleId = data.sampleId,
              name = data.name,
              checkDate = Utils.dataTime2String(time)
            )
          val trashRow = TrashRow(missionId, userId, true)
          extraDataDao
            .insertOrUpdate(extraRow)
            .zip(trashDao.insert(trashRow))
            .map { x =>
              val missionIdDir = tool.getMissionIdDirById(missionId)
              val outFile = new File(tmpDir, "out.txt")
              FileUtils.copyFileToDirectory(outFile, missionIdDir)
              tool.deleteDirectory(tmpDir)
              val json = Json.obj("missionId" -> missionId)
              Ok(json)
            }
        }
      } else {
        tool.deleteDirectory(tmpDir)
        Utils.result2Future(
          Ok(Json.obj("valid" -> "false", "message" -> execCommand.getErrStr))
        )
      }
    } else {
      Utils.result2Future(
        Ok(Json.obj("valid" -> myMessage.valid, "message" -> myMessage.message))
      )
    }

  }

  def export = Action.async { implicit request =>
    val data = formTool.basicInfoForm.bindFromRequest().get
    val pdfData = formTool.pdfDataForm.bindFromRequest().get
    val missionId = formTool.missionIdForm.bindFromRequest().get.missionId
    val userId = tool.getUserId
    val tmpDir = tool.createTempDirectory("tmpDir")
    val outPdfFile = new File(tmpDir, "live_boost_out.pdf")
    val pdf = if (Utils.isWindows) {
      Pdf(
        executablePath = s"${Utils.binPath}/wkhtmltopdf/bin/wkhtmltopdf.exe",
        new PdfConfig {
          orientation := Portrait
          pageSize := "A4"
          pageSize := "Letter"
          marginTop := "0.5in"
          marginBottom := "0in"
          marginLeft := "0.5in"
          marginRight := "0.5in"
        }
      )
    } else
      Pdf(new PdfConfig {
        orientation := Portrait
        pageSize := "A4"
        pageSize := "Letter"
        marginTop := "0.5in"
        marginBottom := "0in"
        marginLeft := "0.5in"
        marginRight := "0.5in"
      })
    missionDao.selectByMissionId(missionId).flatMap { row =>
      val missionIdDir = tool.getMissionIdDirById(missionId)
      val outFile = new File(missionIdDir, "out.txt")
      val pngFile = new File(missionIdDir, "out.png")
      val svgFile = new File(missionIdDir, "out.svg")
      FileUtils.writeStringToFile(svgFile, pdfData.svgStr)
      Utils.svg2png(svgFile)
      val base64 = Utils.getBase64Str(pngFile)
      val logoFile = new File(Utils.dataDir, "logo1.png")
      val logoBase64 = logoFile.base64
      val qrBase64 = new File(Utils.dataDir, "qr_code.png").base64
      val imageInfo =
        Map("barPlot" -> base64, "logo" -> logoBase64, "qrCode" -> qrBase64)
      val htmlFile = new File(tmpDir, "out.html")
      val str = views.html.user.html(data, row, imageInfo, pdfData).toString()
      FileUtils.writeStringToFile(htmlFile, str)
      pdf.run(
        views.html.user.html(data, row, imageInfo, pdfData).toString(),
        outPdfFile
      )
      val bytes = FileUtils.readFileToByteArray(outPdfFile)
      val extraDataRow = ExtraDataRow(
        row.id,
        row.userId,
        data.sample,
        data.unit,
        data.address,
        data.name,
        data.sex,
        data.office,
        data.doctor,
        data.number,
        data.sampleTime,
        data.submitTime,
        data.sampleType,
        data.sampleStatus,
        data.title,
        data.danger,
        data.reporter,
        data.checker,
        data.checkDate,
        data.reportDate
      )
      extraDataDao.insertOrUpdate(extraDataRow).map { x =>
        tool.deleteDirectory(tmpDir)
        Ok(bytes).as("application/pdf")
      }
    }
  }

  def exportPdfs = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[ExportPdfData])
    val userId = Tool.getUserId
    extraDataDao
      .selectByMissionIds(data.missionIds)
      .zip(pdfInfoDao.selectById(userId))
      .zip(missionDao.selectByMissionIds(data.missionIds))
      .flatMap { case ((extraRows, pdfInfo), missionRows) =>
        val tmpDir = tool.createTempDirectory("tmpDir")
        val outZipDir = new File(tmpDir, "live_boost_out").createDirectoryWhenNoExist
        val extraRowMap = extraRows.map(x => (x.id, x)).toMap
        val svgStrMap = data.missionIds.zip(data.svgStrs).toMap
        val extraDataRows = missionRows.map { missionRow =>
          val extraRow = extraRowMap(missionRow.id)
          val basicInfoData = BasicInfoData(
            title = if (extraRow.title.nonBlank) extraRow.title else pdfInfo.title,
            unit = if (extraRow.unit.nonBlank) extraRow.unit else pdfInfo.unit,
            address = if (extraRow.address.nonBlank) extraRow.address else pdfInfo.address,
            reporter = if (extraRow.reporter.nonBlank) extraRow.reporter else pdfInfo.reporter,
            checker = if (extraRow.checker.nonBlank) extraRow.checker else pdfInfo.checker,
            name = extraRow.name,
            sex = extraRow.sex,
            age = missionRow.age,
            office = extraRow.office,
            doctor = extraRow.doctor,
            number = extraRow.number,
            sampleTime = extraRow.sampleTime,
            submitTime = extraRow.submitTime,
            sample = extraRow.sampleId,
            sampleType = extraRow.sampleType,
            sampleStatus = extraRow.sampleStatus,
            checkDate = extraRow.checkDate,
            reportDate =
              if (extraRow.reportDate.nonBlank) extraRow.reportDate
              else
                Utils
                  .dataTime2String(new DateTime()),
            danger = if (extraRow.danger.nonBlank) extraRow.danger else Shared.getDanger(missionRow.score.toDouble)
          )
          val pdfData = PdfData(
            score = missionRow.score,
            result = missionRow.result,
            svgStr = svgStrMap(missionRow.id)
          )
          val pdf = if (Utils.isWindows) {
            Pdf(
              executablePath = s"${Utils.binPath}/wkhtmltopdf/bin/wkhtmltopdf.exe",
              new PdfConfig {
                orientation := Portrait
                pageSize := "A4"
                pageSize := "Letter"
                marginTop := "0.5in"
                marginBottom := "0in"
                marginLeft := "0.5in"
                marginRight := "0.5in"
              }
            )
          } else
            Pdf(new PdfConfig {
              orientation := Portrait
              pageSize := "A4"
              pageSize := "Letter"
              marginTop := "0.5in"
              marginBottom := "0in"
              marginLeft := "0.5in"
              marginRight := "0.5in"
            })
          val pngFile = new File(tmpDir, s"${missionRow.id}.png")
          val svgFile = new File(tmpDir, s"${missionRow.id}.svg")
          FileUtils.writeStringToFile(svgFile, pdfData.svgStr)
          Utils.svg2png(svgFile)
          val base64 = Utils.getBase64Str(pngFile)
          val logoFile = new File(Utils.dataDir, "logo1.png")
          val logoBase64 = logoFile.base64
          val qrBase64 = new File(Utils.dataDir, "qr_code.png").base64
          val imageInfo = Map("barPlot" -> base64, "logo" -> logoBase64, "qrCode" -> qrBase64)
          val outPdfFile = new File(outZipDir, s"${missionRow.sampleId}.pdf")
          pdf.run(views.html.user.html(basicInfoData, missionRow, imageInfo, pdfData).toString(), outPdfFile)
          ExtraDataRow(
            missionRow.id,
            missionRow.userId,
            basicInfoData.sample,
            basicInfoData.unit,
            basicInfoData.address,
            basicInfoData.name,
            basicInfoData.sex,
            basicInfoData.office,
            basicInfoData.doctor,
            basicInfoData.number,
            basicInfoData.sampleTime,
            basicInfoData.submitTime,
            basicInfoData.sampleType,
            basicInfoData.sampleStatus,
            basicInfoData.title,
            basicInfoData.danger,
            basicInfoData.reporter,
            basicInfoData.checker,
            basicInfoData.checkDate,
            basicInfoData.reportDate
          )
        }
        extraDataDao.insertOrUpdates(extraDataRows.toList).map { x =>
          val zipFile = new File(tmpDir, "live_boost_out.zip")
          ZipUtil.pack(outZipDir, zipFile)
          Ok(writeJs(DirData(dir = tmpDir.getAbsolutePath)))
        }
      }
  }

  def batchPredictBefore = Action { implicit request =>
    Ok(views.html.user.batchPredict())
  }

  def downloadExampleFile = Action { implicit request =>
    val data = formTool.fileNameForm.bindFromRequest().get
    val file = new File(Utils.path, s"example/${data.fileName}")
    Ok.sendFile(file)
      .withHeaders(
        CACHE_CONTROL -> "max-age=3600",
        CONTENT_DISPOSITION -> s"attachment; filename=${file.getName}",
        CONTENT_TYPE -> "application/x-download"
      )
  }

  def batchPredict = Action.async(parse.multipartFormData) { implicit request =>
    val tmpDir = tool.createTempDirectory("tmpDir")
    val inputFile = new File(tmpDir, "input.xlsx")
    val file = request.body.file("file").get
    file.ref.moveTo(inputFile, replace = true)
    val myMessage = FileTool.fileCheck(inputFile)
    val responseF = if (myMessage.valid) {
      val userId = tool.getUserId
      val lines = inputFile.xlsxLines().drop(1)
      userDao.selectById(userId).flatMap { user =>
        if (user.remainTimes.isDefined && user.remainTimes.get < lines.size) {
          Future {
            BatchPredict
              .Response(valid = false, message = "你的‘有效次数’不足，请联系管理员充值次数")
          }
        } else {
          val missionIds = ArrayBuffer[Int]()
          val time = new DateTime()
          FileUtils.copyFileToDirectory(
            new File(Utils.dataDir, "LiveBoost_load.RData"),
            tmpDir
          )
          val command =
            s"""
               |Rscript ${(Utils.rPath)}/predict_v1.R
             """.stripMargin
          val execCommand =
            Utils.callScript(tmpDir, shBuffer = ArrayBuffer(command))
          if (execCommand.isSuccess) {
            val outFile = new File(tmpDir, "out.txt")
            val outLines = Utils.file2Lines(outFile)

            val fileContents = outLines.drop(1).map { line =>
              ArrayBuffer(outLines.head, line)
            }
            val missions = fileContents.map { newLines =>
              val resultData = tool.getResultData(newLines)
              val score = Shared.calculateScore(resultData)
              val result = Shared.getResult(score)
              val orinalData = tool.getOrinalData(newLines)
              MissionRow(
                0,
                userId,
                orinalData.sampleId,
                "",
                orinalData.age,
                orinalData.ast,
                orinalData.alt,
                orinalData.plt,
                score.toString(),
                result,
                time
              )
            }
            val f = missionDao.insertAndRetunIds(missions.toList).flatMap {
              missionIds =>
                val etRows = missionIds.zip(fileContents).map {
                  case (missionId, newLines) =>
                    val missionIdDir = tool.getMissionIdDirById(missionId)
                    val outFile = new File(missionIdDir, "out.txt")
                    Utils.lines2File(outFile, newLines)
                    val extraData = tool.getExtraData(outFile)
                    val checkDate =
                      if (StringUtils.isEmpty(extraData.checkDate))
                        Utils.dataTime2String(time)
                      else extraData.checkDate
                    val orinalData = tool.getOrinalData(newLines)
                    val extraRow = tool
                      .getEmptyExtraDataRow(missionId, userId)
                      .copy(
                        sampleId = orinalData.sampleId,
                        name = extraData.name,
                        unit = extraData.unit,
                        address = extraData.address,
                        sex = extraData.sex,
                        office = extraData.office,
                        doctor = extraData.doctor,
                        number = extraData.number,
                        sampleTime = extraData.sampleTime,
                        submitTime = extraData.submitTime,
                        sampleType = extraData.sampleType,
                        sampleStatus = extraData.sampleStatus,
                        title = extraData.title,
                        reporter = extraData.reporter,
                        checker = extraData.checker,
                        checkDate = checkDate,
                        reportDate = extraData.reportDate
                      )
                    val trashRow = TrashRow(missionId, userId, true)
                    (extraRow, trashRow)
                }
                extraDataDao.insertOrUpdates(etRows).map { x =>
                  missionIds
                }
            }
            val missionIds = Utils.execFuture(f)
            tool.deleteDirectory(tmpDir)
            missionDao
              .selectByMissionIds(missionIds)
              .zip(extraDataDao.selectByMissionIds(missionIds))
              .map { case (x, extraRows) =>
                val missionRows = x
                val extraMap = extraRows.map(x => (x.id, x)).toMap
                val t2s = missionRows.map(x =>
                  (
                    x,
                    extraMap.getOrElse(
                      x.id,
                      tool.getEmptyExtraDataRow(x.id, x.userId)
                    )
                  )
                )
                val array = Utils.getArrayByT2s(t2s)
                BatchPredict.Response(array = array, valid = true)
              }
          } else {
            tool.deleteDirectory(tmpDir)
            Future {
              BatchPredict
                .Response(valid = false, message = execCommand.getErrStr)
            }
          }
        }
      }
    } else {
      tool.deleteDirectory(tmpDir)
      Future {
        BatchPredict.Response(valid = myMessage.valid, message = myMessage.message)
      }
    }
    responseF.map { response =>
      Ok(writeJs(response))
    }
  }

  def predictResult = Action.async { implicit request =>
    val data = formTool.missionIdForm.bindFromRequest().get
    missionDao
      .selectByMissionId(data.missionId)
      .zip(extraDataDao.selectByMissionId(data.missionId))
      .map { case (row, extraRow) =>
        val missionIdDir = tool.getMissionIdDirById(data.missionId)
        val outFile = new File(missionIdDir, "out.txt")
        val resultData = tool.getResultData(outFile)
        val extraDataJson = Utils.getJsonByT(extraRow)
        val missionJson = tool.getMissionJson(row, resultData)
        val resultJson = Utils.getJsonByT(resultData)
        val json = Json.obj(
          "missionId" -> data.missionId,
          "sampleId" -> row.sampleId,
          "name" -> row.name,
          "age" -> row.age,
          "extraData" -> extraDataJson,
          "mission" -> missionJson
        ) ++ resultJson.as[JsObject]
        Ok(json)
      }
  }

  def exportPdfByDir = Action { implicit request =>
    val data = Tool.transformRq.transform(reader[DirData])
    val tmpDir = new File(data.dir)
    val outPdfFile = new File(tmpDir, "live_boost_out.zip")
    Ok.sendFile(
      outPdfFile,
      onClose = () => {
        Utils.deleteDirectory(tmpDir)
      }
    ).withHeaders(
      CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${outPdfFile.getName}"),
      CONTENT_TYPE -> "application/x-download"
    )
  }

}
